package edu.scau.mis.system.security.service;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.aliyun.oss.ServiceException;
import com.aliyuncs.utils.StringUtils;
import edu.scau.mis.core.utils.RedisCache;
import edu.scau.mis.system.security.domain.LoginUser;
import edu.scau.mis.system.vo.TokenVo;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class TokenService {

    // AccessToken有效期（2小时）
    private static final int ACCESS_TOKEN_EXPIRE = 2 * 60 * 60 * 1000;
    // RefreshToken有效期（7天）
    private static final int REFRESH_TOKEN_EXPIRE = 7 * 24 * 60 * 60 * 1000;

    private SignatureAlgorithm alg = SignatureAlgorithm.HS512; // 签名算法
    private String secret = "mis_token_secret";

    @Autowired
    private RedisCache redisCache;
    /**
     * 解析request中token获取loginUser
     * @param request http请求
     * @return loginUser
     */
    public LoginUser getLoginUser(HttpServletRequest request) {
        // 1. 从请求头中获取token
        String token = request.getHeader("Authorization");
        LoginUser loginUser = null;
        if(StrUtil.isNotEmpty(token)) {
            // 2. 解析token获得claims
            Claims claims = parseToken(token);
            if (claims != null) {
                // 3. 得到用户信息，生成redisKey
                String subject = claims.getSubject();
                // 4. 根据redisKey从Redis中查询loginUser
                loginUser = redisCache.getCacheObject(subject);
            } else {
                System.out.println("Claims is null after parsing token");
            }
        }
        return loginUser;
    }
    /**
     * 生成token
     * @param subject 主题
     * @param expireTime 过期时间
     * @return 令牌
     */
    public String createToken(String subject,long expireTime) {
        Date issuedAt = new Date(); // 签发时间
        Date expiration = new Date(expireTime); //过期时间
        String token = Jwts.builder()
                .setHeaderParam("typ", "JWT") // 设置header参数
                .setSubject(subject)//设置payload_jwt的主题
                .setIssuedAt(issuedAt) // 设置payload_jwt的签发时间
                .setExpiration(expiration) // 设设置payload_jwt的过期时间
                .signWith(alg, secret) // 设置signature
                .compact();
        return token;
    }

    public static final String TOKEN_PREFIX = "Bearer "; //令牌前缀

    /**
     * 解析token
     * @param token
     * @return
     */
    public Claims parseToken(String token) {
        // 判断token是否有前缀Bearer，有则删除
        if (StrUtil.isNotEmpty(token) && token.startsWith(TOKEN_PREFIX)){
            token = token.replace(TOKEN_PREFIX, "");
        }
        try {
            return Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 生成token并redis存储loginUser
     * @param loginUser
     * @return 令牌
     */
    public Map<String, Object> createAccessToken(LoginUser loginUser){
        String subject = loginUser.getUsername()+ IdUtil.getSnowflakeNextIdStr();
        long expireTime = new Date().getTime() + ACCESS_TOKEN_EXPIRE;
        // 1. 生成token
        String token = createToken(subject, expireTime);
        // 2. 设置loginUser过期时间，根据redisKey将loginUser存入Redis
        loginUser.setExpireTime(expireTime); //设定登录用户过期时间
        redisCache.setCacheObject(subject,loginUser,ACCESS_TOKEN_EXPIRE , TimeUnit.MINUTES);
        Map<String, Object> data = new HashMap<>();
        data.put("token",token);
        data.put("expireTime",expireTime);
        return data;
    }


    public Map<String, Object> createRefreshToken(LoginUser loginUser) {
        long expireTime = new Date().getTime() + REFRESH_TOKEN_EXPIRE;
        String refreshTokenId = "refresh_" + UUID.randomUUID().toString();
        // 生成token
        String token = createToken(refreshTokenId, expireTime);
        loginUser.setExpireTime(expireTime);

        // 存储refreshToken关联的accessToken
        redisCache.setCacheObject("refresh:" + refreshTokenId, loginUser,
                REFRESH_TOKEN_EXPIRE, TimeUnit.MILLISECONDS);

        Map<String, Object> data = new HashMap<>();
        data.put("token",token);
        data.put("expireTime",expireTime);
        return data;
    }

    /**
     * 刷新AccessToken
     * @param refreshToken 刷新令牌
     * @return 新的令牌信息
     */
    public TokenVo refreshToken(String refreshToken) {

        // 移除Bearer前缀（如果存在）
        if (refreshToken.startsWith(TOKEN_PREFIX)) {
            refreshToken = refreshToken.replace(TOKEN_PREFIX, "");
        }

        // 2. 解析refreshToken
        Claims claims = parseToken(refreshToken);
        if (claims == null) {
            throw new ServiceException("无效的刷新令牌");
        }

        // 3. 验证是否为专用refreshToken（前缀检查）
        String tokenId = claims.getSubject();
        if (!tokenId.startsWith("refresh_")) {
            throw new ServiceException("非法的刷新令牌类型");
        }

        // 4. 从Redis获取关联的LoginUser
        LoginUser loginUser = redisCache.getCacheObject("refresh:" + tokenId);
        if (loginUser == null) {
            throw new ServiceException("刷新令牌已失效");
        }

        // 5. 删除旧token记录（防止重复使用）
        redisCache.deleteObject("refresh:" + tokenId);

        // 6. 生成新的token对
        Map<String, Object> accessTokenMap = createAccessToken(loginUser);
        Map<String, Object> refreshTokenMap = createRefreshToken(loginUser);

        // 7. 返回新的token信息
        return TokenVo.builder()
                .accessToken((String) accessTokenMap.get("token"))
                .refreshToken((String) refreshTokenMap.get("token"))
                .expires(new Date((Long) accessTokenMap.get("expireTime")))
                .build();
    }
}
